/**
 * 
 */
package org.gaming.common.service;

import java.lang.reflect.ParameterizedType;
import java.util.Collections;
import java.util.List;

import org.gaming.db.annotation.Table;
import org.gaming.db.repository.BaseRepository;
import org.gaming.db.usecase.SlimDao;
import org.gaming.ruler.redis.RedisCache;
import org.gaming.ruler.redis.RedisEmpty;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;

/**
 * @author YY
 *
 */
public abstract class SystemOneToManyService<T extends SystemOneToManyEntity> {

	protected static Logger logger = LoggerFactory.getLogger(SystemOneToManyService.class);
	
	@Autowired
	protected RedisCache redisCache;
	
	private final Class<T> clazz;
	
	@SuppressWarnings("unchecked")
	public SystemOneToManyService() {
		this.clazz = (Class<T>) ((ParameterizedType) this.getClass().getGenericSuperclass()).getActualTypeArguments()[0];
	}
	
	private BaseRepository<T> repository;
	protected BaseRepository<T> repository() {
		if(repository == null) {
			repository = SlimDao.getRepository(clazz);
		}
		return repository;
	}
	
	/**
	 * 获取单个数据
	 * @param playerId
	 * @param hashId
	 * @return
	 */
	public final T getEntity(long hashId) {
		// 先尝试从缓存中获取单个数据
		T t = this.getCacheData(hashId);
		if (t == null) {
			// 如果缓存中无法获取到单个数据的记录，有可能是数据已被删除，或者是缓存已经过期
			// 因此需要把数据拥有者持有的所有数据重新加载到缓存中
			List<T> list = getEntityList();
			for (T temp : list) {
				if (temp.redisHashKey() == hashId) {
					t = temp;
					break;
				}
			}
		}
		return t;
	}
	
	/**
	 * 获取数据
	 * @param playerId
	 * @return
	 */
	public final List<T> getEntityList() {
		List<T> list = redisCache.getHashAllList(redisKey(), clazz);
		if(list != null) {
			return list;
		}
		String cacheEmptyKey = cacheEmptyKey();
		if (redisCache.getValue(cacheEmptyKey, RedisEmpty.class) != null) {
			return Collections.emptyList();
		}
		list = repository().getAllInDb();
		if (list.isEmpty()) {
			// 当从数据库中查询不出数据时在redis中设定空键，避免重复查数据库获取空数据
			redisCache.insertValueWillTimeout(cacheEmptyKey, RedisEmpty.EMPTY);
		} else {
			redisCache.updateAllHash(redisKey(), list);
			redisCache.setExpire(redisKey());
			redisCache.deleteKey(cacheEmptyKey);
		}
		return list;
	}
	
	/**
	 * 获取玩家的单个数据
	 * @param playerId
	 * @param hashId
	 * @return
	 */
	private T getCacheData(long hashId) {
		return redisCache.getHash(redisKey(), hashId, clazz);
	}
	
	/**
	 * 插入实体数据
	 * @param t
	 */
	public final void insert(T t) {
		repository().insert(t);
		
		String redisKey = redisKey();
		if(this.hasCacheEntity()) {
			redisCache.updateHash(redisKey, t);
			redisCache.setExpire(redisKey);
		}
	}
	
	/**
	 * 插入整个实体列表的数据
	 * @param ts
	 */
	public final void insertAll(List<T> ts) {
		repository().insertAll(ts);
		
		boolean hasCache = this.hasCacheEntity();
		if(hasCache) {
			String redisKey = redisKey();
			redisCache.updateAllHash(redisKey, ts);
			redisCache.setExpire(redisKey);
		}
	}
	
	/**
	 * 更新实体数据
	 * @param t
	 */
	public final void update(T t) {
		repository().update(t);
		
		String redisKey = redisKey();
		if(this.hasCacheEntity()) {
			redisCache.updateHash(redisKey, t);
			redisCache.setExpire(redisKey);
		}
	}
	
	/**
	 * 更新整个实体列表的数据
	 * @param ts
	 */
	public final void updateAll(List<T> ts) {
		repository().updateAll(ts);
		
		boolean hasCache = this.hasCacheEntity();
		if(hasCache) {
			String redisKey = redisKey();
			redisCache.updateAllHash(redisKey, ts);
			redisCache.setExpire(redisKey);
		}
	}
	
	private final String cacheEmptyKey() {
		return redisKey() + "empty";
	}
	
	/**
	 * 是否有缓存的数据
	 * @param ownerId
	 * @return
	 */
	protected boolean hasCacheEntity() {
		return redisCache.hasKey(redisKey()) || redisCache.hasKey(cacheEmptyKey());
	}
	
	private String tableName;
	private final String tableName() {
		if(tableName == null) {
			Table table = this.clazz.getAnnotation(Table.class);
			tableName = table.name();
		}
		return tableName;
	}
	
	protected final String redisKey() {
		return namespace() + ":0:" + tableName();
	}
	
	protected abstract String namespace();
	
	protected void clearCache() {
		redisCache.deleteKey(redisKey());
		redisCache.deleteKey(cacheEmptyKey());
	}
}
